package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;

@Service
@Slf4j
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private HotelMapper hotelMapper;

    /**
     * 酒店分页搜索
     *
     * @param params 请求参数
     * @return
     */
    @Override
    public PageResult search(RequestParams params) {
        try {
            // 1. 创建请求对象
            SearchRequest request = new SearchRequest("hotel");

            //2.准备DSL
            //2.1 创建过滤器
            buildBasicQuery1(params, request);

            //2.2 分页 使用search after 解决深度分页问题
            request.source().from((params.getPage() - 1) * params.getSize()).size(params.getSize());

            //2.3 距离排序
            if (!StringUtils.isEmpty(params.getLocation())) {
                request.source().sort(
                        SortBuilders
                                .geoDistanceSort("location", new GeoPoint(params.getLocation()))
                                .order(SortOrder.ASC)
                                .unit(DistanceUnit.KILOMETERS)
                );
            }

            //3. 执行请求
            SearchResponse search = client.search(request, RequestOptions.DEFAULT);

            //4. 解析响应
            PageResult pageResult = parseHits1(search);

            //5. 返回结果集
            return pageResult;

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 聚合搜索 (数据聚合)
     * 用于展示过滤选项
     *
     * @param requestParams
     * @return
     */
    @Override
    public Map<String, List<String>> filters(RequestParams requestParams) {

        try {
            //1.创建请求对象
            SearchRequest request = new SearchRequest("hotel");

            //2.构造DSL
            //2.1 准备query
            buildBasicQuery1(requestParams, request);
            //2.2 设置size
            request.source().size(0);
            //2.3 聚合
            buildAggsQuery(request);

            //3. 发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            //4. 解析数据
            HashMap<String, List<String>> result = new HashMap<>();
            //4.1 获取城市聚合
            List<String> cityList = getBucketsByName(response, "cityAgg");
            result.put("city", cityList);
            //4.2 获取品牌聚合
            List<String> brandList = getBucketsByName(response, "brandAgg");
            result.put("brand", brandList);
            //4.3 获取星级聚合
            List<String> starList = getBucketsByName(response, "starAgg");
            result.put("starName", starList);

            //返回数据
            return result;

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 搜索建议
     *
     * @param key
     * @return
     */
    @Override
    public List<String> suggestion(String key) {
        //1. 创建请求对象
        SearchRequest request = new SearchRequest("hotel");

        //2. 构造DSL
        request.source().suggest(
                new SuggestBuilder().addSuggestion(
                        "suggestion", // 设置建议名称
                        SuggestBuilders.completionSuggestion("suggestion") // 设置建议字段
                                .skipDuplicates(true) //去重
                                .size(10) //设置数量
                                .prefix(key) //设置前缀
                )
        );

        //3. 发送请求
        SearchResponse response =null;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("搜索建议业务时发送请求异常");
        }

        //4. 解析响应
        ArrayList<String> sugList = new ArrayList<>();
        Suggest suggest = response.getSuggest();
        CompletionSuggestion hotelSuggestion = suggest.getSuggestion("suggestion");
        if (hotelSuggestion.getOptions() == null || CollectionUtils.isEmpty(hotelSuggestion.getOptions())) {
            return sugList;
        }
        List<CompletionSuggestion.Entry.Option> options = hotelSuggestion.getOptions();

        options.stream().forEach(option -> {
            String sug = option.getText().string();
            sugList.add(sug);
        });

        return sugList;
    }

    /**
     * MQ 消费者 删除文档
     * @param id
     */
    @Override
    public void deleteById(Long id) {
        //1. 创建请求对象
        DeleteRequest request = new DeleteRequest("hotel", String.valueOf(id));
        //2. 发送请求
        try {
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.warn("删除文档:{},失败", id);
        }

    }

    /**
     * MQ 消费者 新增或更改文档
     * @param id
     */
    @Override
    public void insertById(Long id) {
        //1.从数据库中查询数据
        Hotel hotel =getById(id);
        if (ObjectUtils.isEmpty(hotel)){
            log.warn("根据id:{},查询数据为空",id);
            return;
        }
        //2. 创建请求对象
        IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());

        //数据封装
        HotelDoc hotelDoc = new HotelDoc(hotel);

        //3.发送请求
        request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        try {
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
            log.warn("新增或更改文档:{},失败", id);
        }
    }

    /**
     * 解析桶数据
     *
     * @param response
     * @return
     */
    private static List<String> getBucketsByName(SearchResponse response, String BucketsName) {
        //4. 解析响应
        //4.1 获取聚合结果
        Aggregations aggregations = response.getAggregations();
        //4.2 根据聚合名解析结果
        Terms brandTerms = aggregations.get(BucketsName);
        //4.3 获取桶集合
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        List<String> brandList = new ArrayList<>();
        buckets.forEach(bucket -> {
            String key = bucket.getKeyAsString();
            brandList.add(key);
        });
        return brandList;
    }

    /**
     * 过滤条件 (数据聚合)
     *
     * @param request
     */
    private void buildAggsQuery(SearchRequest request) {

        // 对品牌进行聚合
        request.source().aggregation(
                AggregationBuilders
                        .terms("brandAgg")
                        .field("brand")
                        .size(100)
        );

        // 对城市进行聚合
        request.source().aggregation(
                AggregationBuilders
                        .terms("cityAgg")
                        .field("city")
                        .size(100)
        );

        // 对星级进行聚合
        request.source().aggregation(
                AggregationBuilders
                        .terms("starAgg")
                        .field("starName")
                        .size(100)
        );

    }


    /**
     * 解析文档数据
     */
    private PageResult parseHits(SearchResponse search) {
        //获取文档数据
        SearchHits hits = search.getHits();
        List<HotelDoc> hotelDocs = new ArrayList<>();
        //1.获取总条数
        Long total = hits.getTotalHits().value;

        //2.获取文档数组
        SearchHit[] hits1 = hits.getHits();

        Arrays.stream(hits1).forEach(hit -> {
            //3.获取文档数据
            String source = hit.getSourceAsString();
            //4.封装文档数据 反序列化
            HotelDoc hotel = JSON.parseObject(source, HotelDoc.class);
            //5.获取排序值
            Object[] sortValues = hit.getSortValues();
            if (!ObjectUtils.isEmpty(sortValues)) {
                hotel.setDistance(sortValues[0]);
            }
            hotelDocs.add(hotel);
        });

        return new PageResult(total, hotelDocs);
    }

    /**
     * 解析文档数据（高亮）
     */
    private PageResult parseHits1(SearchResponse search) {
        //获取文档数据
        SearchHits hits = search.getHits();
        List<HotelDoc> hotelDocs = new ArrayList<>();
        //1.获取总条数
        Long total = hits.getTotalHits().value;

        //2.获取文档数组
        SearchHit[] hits1 = hits.getHits();

        Arrays.stream(hits1).forEach(hit -> {
            //3.获取文档数据
            String source = hit.getSourceAsString();
            //4.封装文档数据 反序列化
            HotelDoc hotel = JSON.parseObject(source, HotelDoc.class);
            //5.获取排序值
            Object[] sortValues = hit.getSortValues();
            if (!ObjectUtils.isEmpty(sortValues)) {
                hotel.setDistance(sortValues[0]);
            }
            //6.高亮
            HighlightField name = hit.getHighlightFields().get("name");
            if (name != null) {
                hotel.setName(name.getFragments()[0].string());
            }
            hotelDocs.add(hotel);
        });

        return new PageResult(total, hotelDocs);
    }

    /**
     * 构造过滤器
     *
     * @param params
     * @return
     */
    private void buildBasicQuery(RequestParams params, SearchRequest request) {
        //1.构造过滤条件
        //创建过滤对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //关键字过滤
        if (!StringUtils.isEmpty(params.getKey())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", params.getKey()));
        } else {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //城市过滤
        if (!StringUtils.isEmpty(params.getCity())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        //星级过滤
        if (!StringUtils.isEmpty(params.getStarName())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        //品牌过滤
        if (!StringUtils.isEmpty(params.getBrand())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        //价格过滤
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQueryBuilder.filter(
                    QueryBuilders.rangeQuery("price")
                            .gte(params.getMinPrice())
                            .lte(params.getMaxPrice())
            );
        }

        //2.算分控制
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                //原始查询
                boolQueryBuilder,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        //其中的一个function score 元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                //过滤条件
                                QueryBuilders.termQuery("isAD", true),
                                //设置函数
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }

        );

        //设置查询条件
        request.source().query(functionScoreQueryBuilder);
    }


    /**
     * 构造过滤器(高亮)
     *
     * @param params
     * @return
     */
    private void buildBasicQuery1(RequestParams params, SearchRequest request) {
        //1.构造过滤条件
        //创建过滤对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //关键字过滤
        if (!StringUtils.isEmpty(params.getKey())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", params.getKey()));
        } else {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        //城市过滤
        if (!StringUtils.isEmpty(params.getCity())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        //星级过滤
        if (!StringUtils.isEmpty(params.getStarName())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        //品牌过滤
        if (!StringUtils.isEmpty(params.getBrand())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        //价格过滤
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQueryBuilder.filter(
                    QueryBuilders.rangeQuery("price")
                            .gte(params.getMinPrice())
                            .lte(params.getMaxPrice())
            );
        }

        //2.算分控制
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                //原始查询
                boolQueryBuilder,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        //其中的一个function score 元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                //过滤条件
                                QueryBuilders.termQuery("isAD", true),
                                //设置函数
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }

        );

        //3.高亮展示
        request.source().highlighter(
                new HighlightBuilder()
                        .field("name")
                        .requireFieldMatch(false)
        );

        //设置查询条件
        request.source().query(functionScoreQueryBuilder);
    }
}

